#include <connectivity/usb.h>
#include <bsp.h>
#include <sheriffos.h>
#include <usbd_cdc_if.h>

extern "C" {
void SystemClock_Config(void) { bsp::SystemClockConfig(); }

uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];
uint8_t UserTxBufferFS[APP_TX_DATA_SIZE];

extern USBD_HandleTypeDef hUsbDeviceFS;

static int8_t CDC_Init_FS();
static int8_t CDC_DeInit_FS();
static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* buff, uint16_t length);
static int8_t CDC_Receive_FS(uint8_t* buff, uint32_t* Len);
static int8_t CDC_TransmitCplt_FS(uint8_t* buff, uint32_t* Len, uint8_t epnum);

USBD_CDC_ItfTypeDef USBD_Interface_fops_FS = {CDC_Init_FS, CDC_DeInit_FS, CDC_Control_FS, CDC_Receive_FS,
                                              CDC_TransmitCplt_FS};

static int8_t CDC_Init_FS(void) {
    USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBufferFS, 0);
    USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);
    return (USBD_OK);
}

static int8_t CDC_DeInit_FS(void) { return (USBD_OK); }

static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* buff, uint16_t length) {
    switch (cmd) {
        case CDC_SEND_ENCAPSULATED_COMMAND:

            break;

        case CDC_GET_ENCAPSULATED_RESPONSE:

            break;

        case CDC_SET_COMM_FEATURE:

            break;

        case CDC_GET_COMM_FEATURE:

            break;

        case CDC_CLEAR_COMM_FEATURE:

            break;

        /*******************************************************************************/
        /* Line Coding Structure */
        /*-----------------------------------------------------------------------------*/
        /* Offset | Field       | Size | Value  | Description */
        /* 0      | dwDTERate   |   4  | Number |Data terminal rate, in bits per
         * second*/
        /* 4      | bCharFormat |   1  | Number | Stop bits */
        /*                                        0 - 1 Stop bit */
        /*                                        1 - 1.5 Stop bits */
        /*                                        2 - 2 Stop bits */
        /* 5      | bParityType |  1   | Number | Parity */
        /*                                        0 - None */
        /*                                        1 - Odd */
        /*                                        2 - Even */
        /*                                        3 - Mark */
        /*                                        4 - Space */
        /* 6      | bDataBits  |   1   | Number Data bits (5, 6, 7, 8 or 16). */
        /*******************************************************************************/
        case CDC_SET_LINE_CODING:

            break;

        case CDC_GET_LINE_CODING:

            break;

        case CDC_SET_CONTROL_LINE_STATE:

            break;

        case CDC_SEND_BREAK:

            break;

        default:
            break;
    }

    return (USBD_OK);
}

static int8_t CDC_Receive_FS(uint8_t* buff, uint32_t* Len) {
    USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &buff[0]);
    USBD_CDC_ReceivePacket(&hUsbDeviceFS);

    os::USB_CDC_ACM_DEVICE::Instance(USB).on_receive.emit(buff, *Len);

    return (USBD_OK);
}

uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len) {
    uint8_t result = USBD_OK;
    /* USER CODE BEGIN 7 */
    auto* hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;
    if (hcdc->TxState != 0) {
        return USBD_BUSY;
    }
    USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
    result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);
    /* USER CODE END 7 */
    return result;
}

/**
 * @brief  CDC_TransmitCplt_FS
 *         Data transmitted callback
 *
 *         @note
 *         This function is IN transfer complete callback used to inform user
 * that the submitted Data is successfully sent over USB.
 *
 * @param  Buf: Buffer of data to be received
 * @param  Len: Number of data received (in bytes)
 * @retval Result of the operation: USBD_OK if all operations are OK else
 * USBD_FAIL
 */
static int8_t CDC_TransmitCplt_FS(uint8_t* buff, uint32_t* Len, uint8_t epnum) {
    uint8_t result = USBD_OK;

    UNUSED(buff);
    UNUSED(Len);
    UNUSED(epnum);

    os::USB_CDC_ACM_DEVICE::Instance(USB).tx_complete.emit(buff, *Len, epnum);

    return result;
}
}

namespace os {
USB_CDC_ACM_DEVICE& USB_CDC_ACM_DEVICE::Instance(USB_TypeDef* usb) {
    if (usb != USB) {
        osError("Invalid usb device");
    }
    static USB_CDC_ACM_DEVICE ins(&hUsbDeviceFS);
    return ins;
}

void USB_CDC_ACM_DEVICE::RegisterRxCallback(int8_t (*cb)(uint8_t*, uint32_t*)) { USBD_Interface_fops_FS.Receive = cb; }

USBD_HandleTypeDef* USB_CDC_ACM_DEVICE::GetHandlePtr() { return m_handle_; }

USBD_StatusTypeDef USB_CDC_ACM_DEVICE::Transmit(uint8_t* Buf, uint16_t Len) {
    return static_cast<USBD_StatusTypeDef>(CDC_Transmit_FS(Buf, Len));
}
} // namespace os